package com.njit;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 工具类：负责 BIN→OTA 打包，以及 OTA→OPTA（ZIP）压缩
 */
public class BinToOtaUtil {

    /**
     * 将 .bin 文件转换为 .ota 文件
     *
     * @param binPath    原始固件二进制文件路径
     * @param otaPath    输出的 OTA 文件路径
     * @param version    固件版本号
     * @param modelName
     * @param minVersion
     * @param maxVersion
     * @throws IOException
     */
    public static void convertBinToOta(Path binPath, Path otaPath, int version, String modelName, Integer minVersion, Integer maxVersion) throws IOException {
        // Files 工具类读取文件的所有字节
        byte[] firmware = Files.readAllBytes(binPath);

        // 2. 计算 CRC32
        CRC32 crc32 = new CRC32();
        crc32.update(firmware);
        long crcValue = crc32.getValue();

        // 3. OTA的数据头是64字节大小的文件
        ByteBuffer header = ByteBuffer.allocate(64)
                .order(ByteOrder.LITTLE_ENDIAN);  //使用大端的方式

        //型号名称固定是16字节的
        byte[] modelBytes = modelName.getBytes(StandardCharsets.UTF_8);

        int fixedLength = 16;
        byte[] fixedModelBytes = new byte[fixedLength];
        int len = Math.min(modelBytes.length, fixedLength);
        //类型是16字节 不足的补充0
        System.arraycopy(modelBytes, 0, fixedModelBytes, 0, len);

        header.put(fixedModelBytes);

        header.putShort((short) version);                     // 固件版本号

        header.putShort((short) minVersion.intValue());        // 固件最低版本

        header.putShort((short) maxVersion.intValue());        //固件最高版本

        header.putInt(firmware.length);             // 固件长度

        //CRC校验序列
        header.putInt((int) crcValue);              // CRC32

        //剩余的都用0来补
        int remaining = 64 - header.position();

        for (int i = 0; i < remaining; i++) {
            header.put((byte) 0x00);
        }

        header.flip();
        // 4. 写入 OTA 文件（先 header，再固件）
        try (FileOutputStream fos = new FileOutputStream(otaPath.toFile());
             FileChannel fc = fos.getChannel()) {
            fc.write(header);
            fc.write(ByteBuffer.wrap(firmware));
        }
        long binSize = Files.size(binPath);

        long otaSize = Files.size(otaPath);
        System.out.printf("bin:  %d bytes%n", binSize);
        System.out.printf("ota:  %d bytes%n", otaSize);
        inspectBin(binPath);
        inspectOta(otaPath);
    }

    /**
     * 将 .ota 文件压缩为 .opta（ZIP 格式）
     *
     * @param otaPath  待压缩的 OTA 文件路径
     * @param optaPath 输出的 OPTA 文件路径
     * @throws IOException
     */
    public static void compressOtaToOpta(Path otaPath, Path optaPath) throws IOException {
        try (FileInputStream fis = new FileInputStream(otaPath.toFile());
             ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(optaPath.toFile()))) {
            // 在 ZIP 里以 ota 文件名作为条目名
            ZipEntry entry = new ZipEntry(otaPath.getFileName().toString());
            zos.putNextEntry(entry);

            byte[] buffer = new byte[4096];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                zos.write(buffer, 0, len);
            }
            zos.closeEntry();
        }
    }

    /**
     * 打印 BIN 文件十六进制内容和长度
     */
    public static void inspectBin(Path binPath) throws IOException {
        byte[] data = Files.readAllBytes(binPath);
        System.out.println("====== BIN 文件内容 ======");
        System.out.println("文件名: " + binPath.getFileName());
        System.out.println("文件大小: " + data.length + " 字节");
        printHex(data, 16);
    }

    /**
     * 打印 OTA 文件头信息、固件数据及 CRC 校验
     */
    public static void inspectOta(Path otaPath) throws IOException {
        byte[] data = Files.readAllBytes(otaPath); //读取所有的字节

        System.out.println("====== OTA 文件内容 ======");
        System.out.println("文件名: " + otaPath.getFileName());
        System.out.println("文件总大小: " + data.length + " 字节");

        if (data.length < 20) {
            System.out.println("文件太小，无法解析头部！");
            return;
        }

        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

// 1. 型号名称（前16字节）
        byte[] modelBytes = new byte[16];
        buffer.get(modelBytes);
        String modelName = new String(modelBytes, StandardCharsets.UTF_8).trim(); // 去掉空字节

// 2. 固件版本号（short）
        int version = buffer.getShort() & 0xFFFF;

// 3. 最低版本（short）
        int minVersion = buffer.getShort() & 0xFFFF;

// 4. 最高版本（short）
        int maxVersion = buffer.getShort() & 0xFFFF;

// 5. padding（short）跳过
        buffer.getShort();

// 6. 固件长度（int）
        int firmwareLen = buffer.getInt();

// 7. CRC32（int）
        int crcFromHeader = buffer.getInt();

// 8. Reserved（int）
        int reserved = buffer.getInt();

// ✅ 打印
        System.out.printf("Model Name:     %s\n", modelName);
        System.out.printf("Version:        %d\n", version);
        System.out.printf("Min Version:    %d\n", minVersion);
        System.out.printf("Max Version:    %d\n", maxVersion);
        System.out.printf("Firmware Length:%d bytes\n", firmwareLen);
        System.out.printf("CRC32 (Header): 0x%08X\n", crcFromHeader);
        System.out.printf("Reserved:       0x%08X\n", reserved);


        // 校验文件是否包含完整固件
//        if (data.length < 64 + firmwareLen) {
//            System.out.println("文件长度不足以包含完整固件内容！");
//            return;
//        }

//        // 提取 firmware 部分（从 offset 64 开始）
//        byte[] firmware = Arrays.copyOfRange(data, 64, 64 + firmwareLen);
//
//        // CRC32 计算
//        CRC32 crc32 = new CRC32();
//        crc32.update(firmware);
//        long realCrc = crc32.getValue();
//        System.out.printf("CRC32 (实际计算): 0x%08X\n", realCrc);
//
//        if ((int) realCrc == crcFromHeader) {
//            System.out.println("✅ CRC32 校验通过！");
//        } else {
//            System.out.println("❌ CRC32 校验失败！");
//        }
//
//        // 打印所有 data 的十六进制内容
//        System.out.println("==== 文件全体十六进制内容（前 512 字节预览） ====");
//        printHex(data, Math.min(data.length, 512)); // 你也可以传 data.length 打印全部
    }

    /**
     * 辅助方法：以十六进制打印前 N 字节
     */
    private static void printHex(byte[] data, int limit) {
        int len = Math.min(limit, data.length);
        for (int i = 0; i < len; i++) {
            System.out.printf("%02X ", data[i]);
            if ((i + 1) % 8 == 0) System.out.print("  ");
        }
        System.out.println();
    }


    /**
     * 将 Intel HEX 文件转换为 BIN 文件
     */
    public static void convertHexToBin(Path hexPath, Path binPath) throws IOException {
        // 第一遍：扫描所有地址段，记录最小和最大地址
        int minAddr = Integer.MAX_VALUE, maxAddr = 0;
        int baseAddr = 0;
        try (BufferedReader reader = Files.newBufferedReader(hexPath)) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith(":")) continue;
                int len = Integer.parseInt(line.substring(1,3), 16);
                int addr = Integer.parseInt(line.substring(3,7), 16);
                int type = Integer.parseInt(line.substring(7,9), 16);

                if (type == 0x00) {
                    int start = baseAddr + addr;
                    minAddr = Math.min(minAddr, start);
                    maxAddr = Math.max(maxAddr, start + len);
                }
                else if (type == 0x04) {
                    baseAddr = Integer.parseInt(line.substring(9,13), 16) << 16;
                }
                else if (type == 0x01) {
                    break; // EOF，终止扫描
                }
                // 可选：处理 02/03 段扩展
            }
        }

        if (minAddr == Integer.MAX_VALUE) {
            throw new IOException("HEX 文件中没有数据段！");
        }

        int totalLength = maxAddr - minAddr;
        byte[] bin = new byte[totalLength];

        // 第二遍：写数据到 [0 .. totalLength) 区间
        baseAddr = 0;
        try (BufferedReader reader = Files.newBufferedReader(hexPath)) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith(":")) continue;
                int len = Integer.parseInt(line.substring(1,3), 16);
                int addr = Integer.parseInt(line.substring(3,7), 16);
                int type = Integer.parseInt(line.substring(7,9), 16);

                if (type == 0x00) {
                    int off = baseAddr + addr - minAddr;
                    for (int i = 0; i < len; i++) {
                        int value = Integer.parseInt(line.substring(9 + i*2, 11 + i*2), 16);
                        bin[off + i] = (byte) value;
                    }
                }
                else if (type == 0x04) {
                    baseAddr = Integer.parseInt(line.substring(9,13), 16) << 16;
                }
                else if (type == 0x01) {
                    break;
                }
            }
        }

        // 写出时只写 [0, totalLength)
        try (FileOutputStream fos = new FileOutputStream(binPath.toFile())) {
            fos.write(bin);
        }
    }

    /**
     * 将 ELF 文件转换为 BIN 文件（使用 objcopy，需安装 GCC 工具链）
     */
    public static void convertElfToBin(Path elfPath, Path binPath) throws IOException, InterruptedException {

         //ProcessBuilder 、
        ProcessBuilder pb = new ProcessBuilder(
                "D:\\exe\\arm-none-eabi-objcopy.exe", "-O", "binary",
                elfPath.toString(), binPath.toString()
        ).redirectErrorStream(true);

        // ③ 启动进程并打印输出（便于调试）
        Process process = pb.start();
        try (BufferedReader reader =
                     new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }

        // ④ 判断退出码
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("ELF 转 BIN 失败，objcopy 退出码: " + exitCode);
        }
    }

    private static Path getAppDir() {
        try {
            URI uri = BinToOtaUtil.class
                    .getProtectionDomain()
                    .getCodeSource()
                    .getLocation()
                    .toURI();
            // 若封装成 exe，则 uri 形如 file:/C:/myapp/MyApp.exe
            // 若为普通 jar，则 uri 形如 file:/C:/myapp/MyApp.jar
            return Paths.get(uri).getParent();
        } catch (URISyntaxException e) {
            throw new IllegalStateException("无法定位程序目录", e);
        }
    }

}


